/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.yammp.util;

import android.database.AbstractCursor;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.util.Log;

/**
 * A variant of MergeCursor that sorts the cursors being merged. If decent
 * performance is ever obtained, it can be put back under android.database.
 */
public class SortCursor extends AbstractCursor {

	private static final String TAG = "SortCursor";
	private Cursor mCursor; // updated in onMove
	private Cursor[] mCursors;
	private int[] mSortColumns;
	private final int ROWCACHESIZE = 64;
	private int mRowNumCache[] = new int[ROWCACHESIZE];
	private int mCursorCache[] = new int[ROWCACHESIZE];
	private int mCurRowNumCache[][];
	private int mLastCacheHit = -1;

	private DataSetObserver mObserver = new DataSetObserver() {

		@Override
		public void onChanged() {

			// Reset our position so the optimizations in move-related code
			// don't screw us over
			mPos = -1;
		}

		@Override
		public void onInvalidated() {

			mPos = -1;
		}
	};

	public SortCursor(Cursor[] cursors, String sortcolumn) {

		mCursors = cursors;

		int length = mCursors.length;
		mSortColumns = new int[length];
		for (int i = 0; i < length; i++) {
			if (mCursors[i] == null) {
				continue;
			}

			// Register ourself as a data set observer
			mCursors[i].registerDataSetObserver(mObserver);

			mCursors[i].moveToFirst();

			// We don't catch the exception
			mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
		}
		mCursor = null;
		String smallest = "";
		for (int j = 0; j < length; j++) {
			if (mCursors[j] == null || mCursors[j].isAfterLast()) {
				continue;
			}
			String current = mCursors[j].getString(mSortColumns[j]);
			if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) {
				smallest = current;
				mCursor = mCursors[j];
			}
		}

		for (int i = mRowNumCache.length - 1; i >= 0; i--) {
			mRowNumCache[i] = -2;
		}
		mCurRowNumCache = new int[ROWCACHESIZE][length];
	}

	@Override
	public void close() {

		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] == null) {
				continue;
			}
			mCursors[i].close();
		}
	}

	@Override
	public void deactivate() {

		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] == null) {
				continue;
			}
			mCursors[i].deactivate();
		}
	}

	@Override
	public byte[] getBlob(int column) {

		return mCursor.getBlob(column);
	}

	@Override
	public String[] getColumnNames() {

		if (mCursor != null)
			return mCursor.getColumnNames();
		else {
			// All of the cursors may be empty, but they can still return
			// this information.
			int length = mCursors.length;
			for (int i = 0; i < length; i++) {
				if (mCursors[i] != null) return mCursors[i].getColumnNames();
			}
			throw new IllegalStateException("No cursor that can return names");
		}
	}

	@Override
	public int getCount() {

		int count = 0;
		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] != null) {
				count += mCursors[i].getCount();
			}
		}
		return count;
	}

	@Override
	public double getDouble(int column) {

		return mCursor.getDouble(column);
	}

	@Override
	public float getFloat(int column) {

		return mCursor.getFloat(column);
	}

	@Override
	public int getInt(int column) {

		return mCursor.getInt(column);
	}

	@Override
	public long getLong(int column) {

		return mCursor.getLong(column);
	}

	@Override
	public short getShort(int column) {

		return mCursor.getShort(column);
	}

	@Override
	public String getString(int column) {

		return mCursor.getString(column);
	}

	@Override
	public boolean isNull(int column) {

		return mCursor.isNull(column);
	}

	@Override
	public boolean onMove(int oldPosition, int newPosition) {

		if (oldPosition == newPosition) return true;

		/*
		 * Find the right cursor Because the client of this cursor (the
		 * listadapter/view) tends to jump around in the cursor somewhat, a
		 * simple cache strategy is used to avoid having to search all cursors
		 * from the start. TODO: investigate strategies for optimizing random
		 * access and reverse-order access.
		 */

		int cache_entry = newPosition % ROWCACHESIZE;

		if (mRowNumCache[cache_entry] == newPosition) {
			int which = mCursorCache[cache_entry];
			mCursor = mCursors[which];
			if (mCursor == null) {
				Log.w(TAG, "onMove: cache results in a null cursor.");
				return false;
			}
			mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
			mLastCacheHit = cache_entry;
			return true;
		}

		mCursor = null;
		int length = mCursors.length;

		if (mLastCacheHit >= 0) {
			for (int i = 0; i < length; i++) {
				if (mCursors[i] == null) {
					continue;
				}
				mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
			}
		}

		if (newPosition < oldPosition || oldPosition == -1) {
			for (int i = 0; i < length; i++) {
				if (mCursors[i] == null) {
					continue;
				}
				mCursors[i].moveToFirst();
			}
			oldPosition = 0;
		}
		if (oldPosition < 0) {
			oldPosition = 0;
		}

		// search forward to the new position
		int smallestIdx = -1;
		for (int i = oldPosition; i <= newPosition; i++) {
			String smallest = "";
			smallestIdx = -1;
			for (int j = 0; j < length; j++) {
				if (mCursors[j] == null || mCursors[j].isAfterLast()) {
					continue;
				}
				String current = mCursors[j].getString(mSortColumns[j]);
				if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) {
					smallest = current;
					smallestIdx = j;
				}
			}
			if (i == newPosition) {
				break;
			}
			if (mCursors[smallestIdx] != null) {
				mCursors[smallestIdx].moveToNext();
			}
		}
		mCursor = mCursors[smallestIdx];
		mRowNumCache[cache_entry] = newPosition;
		mCursorCache[cache_entry] = smallestIdx;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] != null) {
				mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
			}
		}
		mLastCacheHit = -1;
		return true;
	}

	@Override
	public void registerDataSetObserver(DataSetObserver observer) {

		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] != null) {
				mCursors[i].registerDataSetObserver(observer);
			}
		}
	}

	@Override
	public boolean requery() {

		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] == null) {
				continue;
			}

			if (mCursors[i].requery() == false) return false;
		}

		return true;
	}

	@Override
	public void unregisterDataSetObserver(DataSetObserver observer) {

		int length = mCursors.length;
		for (int i = 0; i < length; i++) {
			if (mCursors[i] != null) {
				mCursors[i].unregisterDataSetObserver(observer);
			}
		}
	}
}
